/****************************************************************************************
**  File: Graphics.hpp
**  Author: Asteroth
**  Date: 22/02/2009
****************************************************************************************/

#ifndef __GRAPHICS_HPP__
#define __GRAPHICS_HPP__

#define GD_DEFAULT_DEPTH 0.0f

class Graphics
{
    private:
        core::Matrix4x4 modelView, projection, frustum;

    public:
        Graphics()
        {
            modelView.makeIdentity();
            projection.makeIdentity();
            frustum.makeIdentity();
        }

        ~Graphics()
        {

        }

        inline void clear(bool colorBuffer = true, bool depthBuffer = false)
        {
            glClear(0|(colorBuffer?GL_COLOR_BUFFER_BIT:0)|(depthBuffer?GL_DEPTH_BUFFER_BIT:0));
        }

        inline void setModelView(const core::Matrix4x4 &modelView)
        {
            Graphics::modelView = modelView;
            glMatrixMode(GL_MODELVIEW);
            glLoadMatrixf(Graphics::modelView.M);
        }

        inline void setProjection(const core::Matrix4x4 &projection)
        {
            Graphics::projection = projection;
            glMatrixMode(GL_PROJECTION);
            glLoadMatrixf(Graphics::projection.M);
            glMatrixMode(GL_MODELVIEW);
        }

        inline core::Matrix4x4 getModelView() { return modelView; }
        inline core::Matrix4x4 getProjection() { return projection; }
} graphics;

class Drawing
{
	private:
		core::Color color;
		core::Color backColor;


	public:
		Drawing()
		{
			color = core::Color(255, 255, 255, 255);
			backColor = core::Color(0, 0, 0, 255);
		}

		~Drawing()
		{
		}

		inline void setColor(ubyte red, ubyte green, ubyte blue, ubyte alpha = 255)
		{
			color.Red = red;
			color.Green = green;
			color.Blue = blue;
			color.Alpha = alpha;
		}

		inline void setColor(const core::Color &color)
		{
			this->color = color;
		}

		inline void setBackColor(ubyte red, ubyte green, ubyte blue, ubyte alpha = 0)
		{
			backColor.Red = red;
			backColor.Green = green;
			backColor.Blue = blue;
			backColor.Alpha = alpha;
		}

		inline void setBackColor(const core::Color &color)
		{
			this->backColor = color;
		}

		inline void setLineWidth(float width)
		{
			glLineWidth(width);
		}

		inline core::Color getColor() const {return color;}

		inline core::Color getBackColor() const {return backColor;}

		inline float getLineWidth() const
		{
			float val;
			glGetFloatv(GL_LINE_WIDTH, &val);
			return val;
		}

		inline void enableLineSmooth(bool enable)
		{
			if (enable)
				glEnable(GL_LINE_SMOOTH);
			else
				glDisable(GL_LINE_SMOOTH);
		}

		inline bool isEnabledLineSmooth() {return (bool)glIsEnabled(GL_LINE_SMOOTH);}

		inline void drawLine(float x1, float y1, float x2, float y2, float depth = GD_DEFAULT_DEPTH) const
		{
			glBegin(GL_LINES);
				glColor4ub(color.Red, color.Green, color.Blue, color.Alpha);
				glVertex3f(x1, y1, depth);
				glVertex3f(x2, y2, depth);
			glEnd();
		}

		inline void drawRectangle(float x, float y, float width, float height, bool fill = false, float depth = GD_DEFAULT_DEPTH) const
		{
			if (fill)
			{
				glBegin(GL_QUADS);
					glColor4ub(color.Red, color.Green, color.Blue, color.Alpha);
					glVertex3f(x, y-1, depth);
					glVertex3f(x, y+height-1, depth);
					glVertex3f(x+width, y+height-1, depth);
					glVertex3f(x+width, y, depth);
					glVertex3f(x, y-1, depth);
				glEnd();
			}
			else
			{
				glBegin(GL_LINE_STRIP);
					glColor4ub(color.Red, color.Green, color.Blue, color.Alpha);
					glVertex3f(x, y, depth);
					glVertex3f(x, y+height-1, depth);
					glVertex3f(x+width-1, y+height-1, depth);
					glVertex3f(x+width-1, y, depth);
					glVertex3f(x, y, depth);
				glEnd();
			}
		}

		inline void drawEllipse(float x, float y, float width, float height, bool fill = false, float depth = GD_DEFAULT_DEPTH) const
		{
			width /= 2.0;
			height /= 2.0;
			float div = (int)(12 + (width+height)/10);

			if (fill)
			{
				glBegin(GL_TRIANGLE_FAN);
					glColor4ub(color.Red, color.Green, color.Blue, color.Alpha);
					glVertex3f(x, y, depth);
					for (float i=0; i<=div; i++)
						glVertex3f(x+cos(i*(core::PIm2/div))*width, y+sin(i*(core::PIm2/div))*height, depth);
				glEnd();
			}
			else
			{
				glBegin(GL_LINE_STRIP);
					glColor4ub(color.Red, color.Green, color.Blue, color.Alpha);
					for (float i=0; i<=div; i++)
						glVertex3f(x+cos(i*(core::PIm2/div))*width, y+sin(i*(core::PIm2/div))*height, depth);
				glEnd();
			}
		}

		inline void drawTexture(GLuint textureHandle, float x, float y, float width, float height, float depth = GD_DEFAULT_DEPTH) const
		{
			glBindTexture(GL_TEXTURE_2D, textureHandle);

			glBegin(GL_QUADS);
				glColor4ub(color.Red, color.Green, color.Blue, color.Alpha);
				glTexCoord2f(0.0f, 1.0f);
				glVertex3f(x, y-1, depth);
				glTexCoord2f(0.0f, 0.0f);
				glVertex3f(x, y+height-1, depth);
				glTexCoord2f(1.0f, 0.0f);
				glVertex3f(x+width, y+height-1, depth);
				glTexCoord2f(1.0f, 1.0f);
				glVertex3f(x+width, y, depth);
			glEnd();
		}

} drawing;

class Light
{
	private:
		int lightCnt, maxLightCnt;
		bool* lights;
		float temp[4];

	public:
		Light()
		{
			lightCnt = 0;
			glGetIntegerv(GL_MAX_LIGHTS, &maxLightCnt);
			lights = new bool[maxLightCnt];
			for (int i=0; i<maxLightCnt; i++)
				lights[i] = false;
			glDisable(GL_LIGHT0);
			glDisable(GL_LIGHTING);
			glShadeModel(GL_SMOOTH);
		}

		~Light()
		{
			delete[] lights;
		}

		/// Enable lighting.
		inline void enable() {glEnable(GL_LIGHTING);}
		/// Enable a specific light.
		inline void enable(uint light) {glEnable(light);}
		/// Disable lighting.
		inline void disable() {glDisable(GL_LIGHTING);}
		/// Disable a specific light.
		inline void disable(uint light) {glDisable(light);}

		/// The max number of lights.
		inline int maxLightCount() {return maxLightCnt;}
		/// The number of lights created.
		inline int lightCount() {return lightCnt;}

		/// Creates a light and returns it's handle (0 on failure).
		inline uint create()
		{
			if (lightCnt<maxLightCnt)
			{
				for (int i=0; i<maxLightCnt; i++)
					if (lights[i]==false)
					{
						lights[i] = true;
						glEnable(GL_LIGHT0+i);
						return GL_LIGHT0+i;
					}
			}

			return 0;
		}
		/// Destroys a light.
		inline void destroy(uint light)
		{
			if (light>=GL_LIGHT0 && light<GL_LIGHT0+(uint)maxLightCnt)
			{
				glDisable(light);
				lights[light-GL_LIGHT0] = false;
			}
		}

		/// Set light settings.
		inline void setPosition(uint light, const core::Quaternion &pos)
		{
			temp[0] = pos.X;
			temp[1] = pos.Y;
			temp[2] = pos.Z;
			temp[3] = pos.W;
			glLightfv(light, GL_POSITION, temp);
		}

		inline void setDiffuse(uint light, const core::Quaternion &col)
		{
			temp[0] = col.X;
			temp[1] = col.Y;
			temp[2] = col.Z;
			temp[3] = col.W;
			glLightfv(light, GL_DIFFUSE, temp);
		}

		inline void setAmbient(uint light, const core::Quaternion &col)
		{
			temp[0] = col.X;
			temp[1] = col.Y;
			temp[2] = col.Z;
			temp[3] = col.W;
			glLightfv(light, GL_AMBIENT, temp);
		}

		inline void setSpecular(uint light, const core::Quaternion &col)
		{
			temp[0] = col.X;
			temp[1] = col.Y;
			temp[2] = col.Z;
			temp[3] = col.W;
			glLightfv(light, GL_SPECULAR, temp);
		}

		inline void setDirection(uint light, const core::Quaternion &dir)
		{
			temp[0] = dir.X;
			temp[1] = dir.Y;
			temp[2] = dir.Z;
			temp[3] = dir.W;
			glLightfv(light, GL_SPOT_DIRECTION, temp);
		}

		inline void setSpot(uint light, float cutoff = 180.0f, float exponent = 0.0f)
		{
			glLightf(light, GL_SPOT_CUTOFF, cutoff);
			glLightf(light, GL_SPOT_EXPONENT, exponent);
		}

		inline void setAttenuation(uint light, float constant = 1.0f, float linear = 0.0f, float quadric = 0.0f)
		{
			glLightf(light, GL_CONSTANT_ATTENUATION, constant);
			glLightf(light, GL_LINEAR_ATTENUATION, linear);
			glLightf(light, GL_QUADRATIC_ATTENUATION, quadric);
		}

		inline void setAmbientRadiation(const core::Quaternion &col)
		{
			temp[0] = col.X;
			temp[1] = col.Y;
			temp[2] = col.Z;
			temp[3] = col.W;
			glLightModelfv(GL_LIGHT_MODEL_AMBIENT, temp);
		}
} light;

class Material
{
	private:

	public:

} material;

class Enable
{
	// Enable alpha test.
	// function: GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, and GL_ALWAYS
	// reference: the value to compare with
	static inline void alphaTest(GLenum function = GL_GREATER, GLclampf reference = 0)
	{
		glEnable(GL_ALPHA_TEST);
		glAlphaFunc(function, reference);
	}

	static inline void autoNormal()
	{
		glEnable(GL_AUTO_NORMAL);
	}

	// Enable alpha blending.
	// sourceFactor: GL_ZERO, GL_ONE, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA,
	//      GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, and GL_SRC_ALPHA_SATURATE
	// destFactor: GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA,
	//      GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, and GL_ONE_MINUS_DST_ALPHA
	static inline void blend(GLenum sourceFactor, GLenum destFactor)
	{
		glEnable(GL_BLEND);
		glBlendFunc(sourceFactor, destFactor);
	}

	// Enable clipping planes.
	// clipPlane: GL_CLIP_PLANEi(6)
	// equation: GLdouble[4] - equation of the plane
	static inline void clipPlane(GLenum clipPlane, const GLdouble *equation)
	{
		glEnable(clipPlane);
		glClipPlane(clipPlane, equation);
	}

	// Enable color operation (default GL_SET).
	// operation: GL_CLEAR, GL_SET, GL_COPY, GL_COPY_INVERTED, GL_NOOP, GL_INVERT, GL_AND,
	//      GL_NAND, GL_OR, GL_NOR, GL_XOR, GL_EQUIV, GL_AND_REVERSE, GL_AND_INVERTED,
	//      GL_OR_REVERSE, and GL_OR_INVERTED
	static inline void colorLogicOperation(GLenum operation)
	{
		glEnable(GL_COLOR_LOGIC_OP);
		glLogicOp(operation);
	}

	// Enable color material (makes the glColor*() function to set material properties).
	// face: GL_FRONT, GL_BACK, and GL_FRONT_AND_BACK
	// mode: GL_EMISSION, GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, and GL_AMBIENT_AND_DIFFUSE
	static inline void colorMaterial(GLenum face, GLenum mode)
	{
		glColorMaterial(face, mode);
		glEnable(GL_COLOR_MATERIAL);
	}

	// Enable cull face.
	// face: GL_FRONT, GL_BACK or GL_FRONT_AND_BACK
	static inline void cullFace(GLenum face = GL_BACK)
	{
		glEnable(GL_CULL_FACE);
		glCullFace(face);
	}

	// Enable depth test.
	// function: GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, and GL_ALWAYS
	// near, far: 0..1
	static inline void depthTest(GLenum function = GL_LEQUAL, GLclampd nearDist = 0, GLclampd farDist = 1)
	{
		glEnable(GL_DEPTH_TEST);
		glDepthFunc(function);
		glDepthRange(nearDist, farDist);
	}

	// Enable dithering.
	static inline void dither()
	{
		glEnable(GL_DITHER);
	}

	// Enable fog.
	// mode: GL_LINEAR, GL_EXP, and GL_EXP2
	static inline void fog(GLenum mode = GL_EXP, GLfloat density = 0.35f, GLfloat start = 1, GLfloat end = 5)
	{
		glFogi(GL_FOG_MODE, mode);
		glFogf(GL_FOG_DENSITY, density);
		glFogf(GL_FOG_START, start);
		glFogf(GL_FOG_END, end);
		glEnable(GL_FOG);
	}

	// Enable index logic operation (a pre historic functionality?).
	// operation: GL_CLEAR, GL_SET, GL_COPY, GL_COPY_INVERTED, GL_NOOP, GL_INVERT, GL_AND,
	//      GL_NAND, GL_OR, GL_NOR, GL_XOR, GL_EQUIV, GL_AND_REVERSE, GL_AND_INVERTED,
	//      GL_OR_REVERSE, and GL_OR_INVERTED
	static inline void indexLogicOperation(GLenum operation)
	{
		glEnable(GL_INDEX_LOGIC_OP);
		glLogicOp(operation);
	}

	// Enable line stipple (example pattern: 1110111010011001, resulting drawed line pattern: --- --- -  --  -).
	static inline void lineStipple(GLint factor, GLushort pattern)
	{
		glEnable(GL_LINE_STIPPLE);
		glLineStipple(factor, pattern);
	}

	// Enable automatic normalization of normals set with glNormal*().
	static inline void normalize()
	{
		glEnable(GL_NORMALIZE);
	}

	// Enable antialiasing with points, lines and polygons.
	static inline void pointSmooth()
	{
		glEnable(GL_POINT_SMOOTH);
	}

	static inline void lineSmooth()
	{
		glEnable(GL_LINE_SMOOTH);
	}

	static inline void polygonSmooth()
	{
		glEnable(GL_POLYGON_SMOOTH);
	}

	// Enable scissor test.
	static inline void scissorTest(GLint x, GLint y, GLsizei width, GLsizei height)
	{
		glEnable(GL_SCISSOR_TEST);
		glScissor(x, y, width, height);
	}

	// Enable stencil test.
	static inline void stencilTest(GLenum function = GL_ALWAYS, GLint reference = 0, GLuint mask = 1,
		GLenum failOp = GL_KEEP, GLenum zfailOp = GL_KEEP, GLenum zpassOp = GL_KEEP)
	{
		glEnable(GL_STENCIL_TEST);
		glStencilFunc(function, reference, mask);
		glStencilOp(failOp, zfailOp, zpassOp);
	}

	// Enable texturing.
	static inline void texture1D()
	{
		glEnable(GL_TEXTURE_1D);
	}

	static inline void texture2D()
	{
		glEnable(GL_TEXTURE_2D);
	}

	static inline void texture3D()
	{
		glEnable(GL_TEXTURE_3D_EXT);
	}

	// Enable simulated wiregrame.
	static inline void wireframe()
	{
		glPolygonMode(GL_FRONT, GL_LINE);
	}
} enable;

class Disable
{
	static inline void alphaTest()                      { glDisable(GL_ALPHA_TEST); }
	static inline void autoNormal()                     { glDisable(GL_AUTO_NORMAL); }
	static inline void blend()                          { glDisable(GL_BLEND); }
	static inline void clipPlane(GLenum clipPlane)      { glDisable(clipPlane); }
	static inline void colorLogicOperation()            { glDisable(GL_COLOR_LOGIC_OP); }
	static inline void colorMaterial()                  { glDisable(GL_COLOR_MATERIAL); }
	static inline void cullFace()                       { glDisable(GL_CULL_FACE); }
	static inline void depthTest()                      { glDisable(GL_DEPTH_TEST); }
	static inline void dither()                         { glDisable(GL_DITHER); }
	static inline void fog()                            { glDisable(GL_FOG); }
	static inline void indexLogicOperation()            { glDisable(GL_INDEX_LOGIC_OP); }
	static inline void lineStipple()                    { glDisable(GL_LINE_STIPPLE); }
	static inline void normalize()                      { glDisable(GL_NORMALIZE); }
	static inline void pointSmooth()                    { glDisable(GL_POINT_SMOOTH); }
	static inline void lineSmooth()                     { glDisable(GL_LINE_SMOOTH); }
	static inline void polygonSmooth()                  { glDisable(GL_POLYGON_SMOOTH); }
	static inline void scissorTest()                    { glDisable(GL_SCISSOR_TEST); }
	static inline void stencilTest()                    { glDisable(GL_STENCIL_TEST); }
	static inline void texture1D()                      { glDisable(GL_TEXTURE_1D); }
	static inline void texture2D()                      { glDisable(GL_TEXTURE_2D); }
	static inline void texture3D()                      { glDisable(GL_TEXTURE_3D_EXT); }
	static inline void wireframe()                      { glPolygonMode(GL_FRONT, GL_FILL); }
} disable;

#endif
